//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

//#include "stdafx.h"
#include "EMSocket.h"
#include "opcodes.h"

IMPLEMENT_DYNAMIC_CLASS(CEMSocket,wxSocketClient)

//#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#if 0
namespace {
	inline void EMTrace(char* fmt, ...) {
#ifdef _DEBUG
		va_list argptr;
		char bufferline[512];
		va_start(argptr, fmt);
		_vsnprintf(bufferline, 512, fmt, argptr);
		va_end(argptr);
		//(Ornis+)
		char osDate[30],osTime[30]; 
		char temp[1024]; 
		_strtime( osTime );
		_strdate( osDate );
		int len = _snprintf(temp,1021,"%s %s: %s",osDate,osTime,bufferline);
		temp[len++] = 0x0d;
		temp[len++] = 0x0a;
		temp[len+1] = 0;
		HANDLE hFile = CreateFile("c:\\EMSocket.log",           // open MYFILE.TXT 
                GENERIC_WRITE,              // open for reading 
                FILE_SHARE_READ,           // share for reading 
                NULL,                      // no security 
                OPEN_ALWAYS,               // existing file only 
                FILE_ATTRIBUTE_NORMAL,     // normal file 
                NULL);                     // no attr. template 
  
		if (hFile != INVALID_HANDLE_VALUE) 
		{ 
			DWORD nbBytesWritten = 0;
			SetFilePointer(hFile, 0, NULL, FILE_END);
			BOOL b = WriteFile(
				hFile,                    // handle to file
				temp,                // data buffer
				len,     // number of bytes to write
				&nbBytesWritten,  // number of bytes written
				NULL        // overlapped buffer
			);
			CloseHandle(hFile);
		}
#else
		va_list argptr;
		va_start(argptr, fmt);
		va_end(argptr);
#endif
	}
}
#endif

CEMSocket::CEMSocket(void)
  : wxSocketClient(wxSOCKET_NOWAIT/*wxSOCKET_BLOCK*/)
{
	limitenabled = false;
	downloadlimit = 0;
	byConnected = ES_NOTCONNECTED;
	sendbuffer = 0;
	sendblen = 0;
	sent = 0;
	readbuf=NULL;
	readbuf_size=0;
	m_bLinkedPackets = false;
}

CEMSocket::~CEMSocket(){
   //EMTrace("CEMSocket::~CEMSocket() on %d",(SOCKET)this);
  //printf("CEMSocket::~CEMSocket() on %d\n",this);
  SetNotify(0);
  Notify(FALSE);
  ClearQueues();
  OnClose(0);
  //AsyncSelect(0);
}


void CEMSocket::ClearQueues(){
  //EMTrace("CEMSocket::ClearQueues on %d",(SOCKET)this);
	for (POSITION pos = controlpacket_queue.GetHeadPosition();pos != 0;controlpacket_queue.GetNext(pos))
		delete controlpacket_queue.GetAt(pos);
	controlpacket_queue.RemoveAll();
	for (POSITION pos = standartpacket_queue.GetHeadPosition();pos != 0;standartpacket_queue.GetNext(pos))
		delete standartpacket_queue.GetAt(pos);
	standartpacket_queue.RemoveAll();
	if (readbuf){
		delete[] readbuf;
		readbuf = 0;
		readbuf_size = 0;
	}
	if (sendbuffer)
		delete[] sendbuffer;
	sendbuffer = 0;
	sendblen = 0;
	sent = 0;
	limitenabled = false;
	m_bLinkedPackets = false;
}

void CEMSocket::OnClose(int nErrorCode){
	byConnected = ES_DISCONNECTED;
	//CAsyncSocket::OnClose(nErrorCode);
	ClearQueues();
};

#if 0
BOOL CEMSocket::AsyncSelect(long lEvent){
	EMTrace("CEMSocket::AsyncSelect on %d",(SOCKET)this);
	if (lEvent&FD_READ)
		EMTrace("  FD_READ");
	if (lEvent&FD_CLOSE)
		EMTrace("  FD_CLOSE");
	if (lEvent&FD_WRITE)
		EMTrace("  FD_WRITE");
	if (m_hSocket != INVALID_SOCKET)
		return CAsyncSocket::AsyncSelect(lEvent);
	return true;
}
#endif

extern DWORD GetTickCount();
#if 0
void CEMSocket::OnReceive(int nErrorCode){
  if (nErrorCode){
    OnError(nErrorCode);
    return;
  }
  if (byConnected == ES_DISCONNECTED)
    return;
  else
    byConnected = ES_CONNECTED;
  
  if (dataneeded == 0){
    dataneeded = 6;
    datawaiting = 0;
  } //
  
  if (!receivedpacket){
    assert (dataneeded < 7);
    //printf("getting header: dn:%d dw:%d\n",dataneeded,datawaiting);
    //Receive(header+datawaiting,dataneeded-datawaiting);
    if(!Ok()) {
      OnError(1);
      return;
    }
    // zero what we need
    memset(header+datawaiting,0,dataneeded-datawaiting);
    Read(header+datawaiting,dataneeded-datawaiting);
    uint32 len=LastCount();
    //if (len == (uint32)-1)
    if(Error())
      return;
    if(len<1) 
      return;
    datawaiting += len;
    if (datawaiting != dataneeded)
      return;
    receivedpacket = new Packet(header);
    dataneeded = 0;
    datawaiting = 0;
    
    switch (receivedpacket->prot){
    case OP_EDONKEYPROT:
    case OP_PACKEDPROT:
    case OP_EMULEPROT:
      break;
    default:
      //EMTrace("CEMSocket::OnReceive ERROR Wrong header");
      delete receivedpacket;
      receivedpacket = 0;
      OnError(ERR_WRONGHEADER);
      return;
    }
    
    //assert(receivedpacket->size < 200000);
    if (receivedpacket->size > 200000){
      delete receivedpacket;
      receivedpacket = 0;
      OnError(ERR_TOOBIG);
      return;
    }
    dataneeded = receivedpacket->size;
    assert (readbuffer == NULL);
    readbuffer = new char[receivedpacket->size+1];
    memset(readbuffer,0,receivedpacket->size+1);
  }
  //assert (readbuffer != NULL);
  //assert (receivedpacket != NULL);
  if (!readbuffer){
    OnError(3);
    return;
  }
  
  uint32 toreceive = dataneeded-datawaiting;
  if (limitenabled && (toreceive > downloadlimit))
    toreceive = downloadlimit;
  uint32 len = 0;
  //printf("Getting content: dw:%d tr:%d dn:%d\n",datawaiting,toreceive,dataneeded);
  if (toreceive) {
    //Receive(readbuffer+datawaiting,toreceive);
    if(!Ok()) {
      OnError(1);
      return;
    }
    Read(readbuffer+datawaiting,toreceive);
    len=LastCount();
  } else {
    len=0;
  }
  //if (len == (uint32)(-1))
  if(Error()) 
    return;
  if (limitenabled)
    downloadlimit -= len;
  datawaiting += len;
  
  if (datawaiting != dataneeded)
    return;
  dataneeded = 6;
  datawaiting = 0;
  
  //assert (AfxIsValidAddress(readbuffer,receivedpacket->size,true));
  receivedpacket->pBuffer = readbuffer;
  readbuffer = 0;
  
  Packet* toprocess = receivedpacket;
  receivedpacket = 0;
  
  PacketReceived(toprocess);
  delete toprocess;
}
#endif

void CEMSocket::OnReceive(int nErrorCode){
	// the 2 meg size was taken from another place
	static char GlobalReadBuffer[2000000];

	if (nErrorCode){
		OnError(nErrorCode);
		return;
	}
	if (byConnected == ES_DISCONNECTED)
		return;
	else
		byConnected = ES_CONNECTED;

	uint32 readMax = sizeof(GlobalReadBuffer) - readbuf_size;

	// Buffer overflow
	if (readMax == 0) {
		delete readbuf;
		readbuf = NULL;
		readbuf_size = 0;
		OnError(ERR_TOOBIG);
		return;
	}

	if (limitenabled && readMax > downloadlimit) {
		readMax = downloadlimit;
	}

	// We attempt to read up to 2 megs at a time (minus whatever is in our internal read buffer)
	//uint32 ret = Receive(GlobalReadBuffer + readbuf_size, readMax);
	Read(GlobalReadBuffer+readbuf_size,readMax);
	uint32 ret=LastCount();

	if (Error() || ret == 0) {
		return;
	}

	if (limitenabled) {
		downloadlimit -= ret;
	}

	// Copy over our temporary read buffer into the global read buffer for processing
	if (readbuf) {
  		memcpy(GlobalReadBuffer, readbuf, readbuf_size);
		ret += readbuf_size;
		delete[] readbuf;
		readbuf = NULL;
		readbuf_size = 0;
	}

	char *rptr = GlobalReadBuffer;
	char *rend = GlobalReadBuffer + ret;

	// Loop, processing packets until we run out of them
	while (rend - rptr >= 6) {
		Packet *packet = new Packet(rptr);

		rptr += 6;

		if (packet->size > sizeof(GlobalReadBuffer)) {
			delete packet;
			OnError(ERR_TOOBIG);
			return;
		}

		if (packet->size > rend - rptr) {
			rptr -= 6;
			delete packet;
			break;
		}


		char *packetBuffer = new char[packet->size + 1];
		memcpy(packetBuffer, rptr, packet->size);

		rptr += packet->size;
		packet->pBuffer = packetBuffer;
		PacketReceived(packet);
		delete packet;
	}

	// Finally, if there is any data left over, save it for next time
	//ASSERT(rptr <= rend);
	if (rptr != rend) {
		readbuf_size = rend - rptr;
		readbuf = new char[readbuf_size];
		memcpy(readbuf, rptr, readbuf_size);
	}	
}

void CEMSocket::SetDownloadLimit(uint32 limit){
	limitenabled = true;
	downloadlimit = limit;
	OnReceive(0);
}

void CEMSocket::DisableDownloadLimit(){
	limitenabled = false;
	OnReceive(0);
}

bool CEMSocket::SendPacket(Packet* packet, bool delpacket,bool controlpacket){
	if (!delpacket){
		Packet* copy = new Packet(packet->opcode,packet->size);
		memcpy(copy->pBuffer,packet->pBuffer,packet->size);
		packet = copy;
	}			
	if ( ( (!IsConnected()) || IsBusy() ) || ( m_bLinkedPackets && controlpacket ) ){
		if (controlpacket){
		  controlpacket_queue.AddTail(packet); //AddTail(packet);
			return true;
		}
		else{
		  standartpacket_queue.AddTail(packet); //AddTail(packet);
			return true;
		}
	}
	bool bCheckControlQueue = false;
	if (packet->IsLastSplitted() ){
		m_bLinkedPackets = false;
		bCheckControlQueue = true;
	}
	else if (packet->IsSplitted())
		m_bLinkedPackets = true;
	else if (m_bLinkedPackets)
		assert (false);

	//printf("Sending packet (%d,opcode=%x)\n",packet->GetRealPacketSize(),packet->opcode);
	//assert(packet->GetRealPacketSize()!=1300);

	Send(packet->DetachPacket(),packet->GetRealPacketSize());

	delete packet;
	if (!IsBusy() && bCheckControlQueue)
		OnSend(0);
	return true;
}

void CEMSocket::OnSend(int nErrorCode){
  if (nErrorCode) { // || Error()){
    OnError(nErrorCode);
    return;
  }
  if (byConnected == ES_DISCONNECTED) {
    return;
  }
  else {
    byConnected = ES_CONNECTED;
  }
  
  if (IsBusy())
    Send(0,0,0);
  if (IsBusy()) {
    return;
  }

	while (controlpacket_queue.GetHeadPosition() != 0 && (!IsBusy()) && IsConnected() && !m_bLinkedPackets){
		Packet* cur_packet = controlpacket_queue.GetHead();
//		EMTrace("CEMSocket::OnSend sending control packet on %d, size=%u",(SOCKET)this, cur_packet->GetRealPacketSize());
		Send(cur_packet->DetachPacket(),cur_packet->GetRealPacketSize());
		controlpacket_queue.RemoveHead();
		delete cur_packet;
	}

	while (standartpacket_queue.GetHeadPosition() != 0 && (!IsBusy()) && IsConnected()){
		Packet* cur_packet = standartpacket_queue.GetHead();
		if (cur_packet->IsLastSplitted() )
			m_bLinkedPackets = false;
		else if (cur_packet->IsSplitted())
			m_bLinkedPackets = true;
		else if (m_bLinkedPackets)
			assert (false);
//		EMTrace("CEMSocket::OnSend sending standart packet on %d, size=%u",(SOCKET)this, cur_packet->GetRealPacketSize());
		Send(cur_packet->DetachPacket(),cur_packet->GetRealPacketSize());
		standartpacket_queue.RemoveHead();
		delete cur_packet;
	}

	while (controlpacket_queue.GetHeadPosition() != 0 && (!IsBusy()) && IsConnected() && !m_bLinkedPackets){
		Packet* cur_packet = controlpacket_queue.GetHead();
//		EMTrace("CEMSocket::OnSend sending control packet on %d, size=%u",(SOCKET)this, cur_packet->GetRealPacketSize());
		Send(cur_packet->DetachPacket(),cur_packet->GetRealPacketSize());
		controlpacket_queue.RemoveHead();
		delete cur_packet;
	} 
}

#include <signal.h>
int CEMSocket::Send(char* lpBuf,int nBufLen,int nFlags){
  assert (sendbuffer == NULL || lpBuf == NULL );
  if (lpBuf){
    sendbuffer = lpBuf;
    sendblen = nBufLen;
    sent = 0;
  }
  while (true){
    uint32 tosend = sendblen-sent;
    if (tosend > MAXFRAGSIZE)
      tosend = MAXFRAGSIZE;
    assert (tosend != 0);
    
    signal(SIGPIPE,SIG_IGN);
    wxSocketBase::Write(sendbuffer+sent,tosend);

    uint32 result=LastCount();
    //if (result == (uint32)-1){
    if(Error()) {
      uint32 error = LastError(); //GetLastError();
      if (error == wxSOCKET_WOULDBLOCK){
	break;
      }
      else{
	//OnError(error);
	return -1;
      }
    }
    sent += result;
    assert (sent <= sendblen);
    if (sent == sendblen){
      delete[] sendbuffer;
      sendbuffer = 0;
      sent = 0;
      sendblen = 0;
      break;
    }

  }
  return 0;
}

// pach2:
// written this overriden Receive to handle transparently FIN notifications coming from calls to recv()
// This was maybe(??) the cause of a lot of socket error, notably after a brutal close from peer
// also added trace so that we can debug after the fact ...
int CEMSocket::Receive(void* lpBuf, int nBufLen, int nFlags)
{
#if 0
  //	EMTrace("CEMSocket::Receive on %d, maxSize=%d",(SOCKET)this,nBufLen);
  //int recvRetCode = CAsyncSocket::Receive(lpBuf,nBufLen,nFlags);
  
  switch (recvRetCode) {
  case 0:
    EMTrace("CEMSocket::##Received FIN on %d, maxSize=%d",(SOCKET)this,nBufLen);
    // FIN received on socket // Connection is being closed by peer
    //ASSERT (false);
    if ( 0 == AsyncSelect(FD_CLOSE|FD_WRITE) ) { // no more READ notifications ...
      int waserr = GetLastError(); // oups, AsyncSelect failed !!!
      assert(false);
    }
    return 0;
  case SOCKET_ERROR:
    switch(GetLastError()) {
    case WSANOTINITIALISED:
      ASSERT(false);
      EMTrace("CEMSocket::OnReceive:A successful AfxSocketInit must occur before using this API.");
      break;
    case WSAENETDOWN:
      ASSERT(true);
      EMTrace("CEMSocket::OnReceive:The socket %d received a net down error",(SOCKET)this);
      break;
    case WSAENOTCONN: // The socket is not connected. 
      EMTrace("CEMSocket::OnReceive:The socket %d is not connected",(SOCKET)this);
      break;
    case WSAEINPROGRESS:   // A blocking Windows Sockets operation is in progress. 
      EMTrace("CEMSocket::OnReceive:The socket %d is blocked",(SOCKET)this);
      break;
    case WSAEWOULDBLOCK:   // The socket is marked as nonblocking and the Receive operation would block. 
      EMTrace("CEMSocket::OnReceive:The socket %d would block",(SOCKET)this);
      break;
    case WSAENOTSOCK:   // The descriptor is not a socket. 
      EMTrace("CEMSocket::OnReceive:The descriptor %d is not a socket (may have been closed or never created)",(SOCKET)this);
      break;
    case WSAEOPNOTSUPP:  // MSG_OOB was specified, but the socket is not of type SOCK_STREAM. 
      break;
    case WSAESHUTDOWN:   // The socket has been shut down; it is not possible to call Receive on a socket after ShutDown has been invoked with nHow set to 0 or 2. 
      EMTrace("CEMSocket::OnReceive:The socket %d has been shut down",(SOCKET)this);
      break;
    case WSAEMSGSIZE:   // The datagram was too large to fit into the specified buffer and was truncated. 
      EMTrace("CEMSocket::OnReceive:The datagram was too large to fit and was truncated (socket %d)",(SOCKET)this);
      break;
    case WSAEINVAL:   // The socket has not been bound with Bind. 
      EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this);
      break;
    case WSAECONNABORTED:   // The virtual circuit was aborted due to timeout or other failure. 
      EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this);
      break;
    case WSAECONNRESET:   // The virtual circuit was reset by the remote side. 
      EMTrace("CEMSocket::OnReceive:The socket %d has not been bound",(SOCKET)this);
      break;
    default:
      EMTrace("CEMSocket::OnReceive:Unexpected socket error %x on socket %d",GetLastError(),(SOCKET)this);
      break;
    }
    return SOCKET_ERROR;
  default:
    //		EMTrace("CEMSocket::OnReceive on %d, receivedSize=%d",(SOCKET)this,recvRetCode);
    return recvRetCode;
  }
  return SOCKET_ERROR;
#endif
}
